/* jcifs smb client library in Java
 * Copyright (C) 2000  "Michael B. Allen" <jcifs at samba dot org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package jcifs.smb1.smb1;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jcifs.smb1.Config;
import jcifs.smb1.UniAddress;
import jcifs.smb1.dcerpc.DcerpcHandle;
import jcifs.smb1.dcerpc.msrpc.MsrpcDfsRootEnum;
import jcifs.smb1.dcerpc.msrpc.MsrpcShareEnum;
import jcifs.smb1.dcerpc.msrpc.MsrpcShareGetInfo;
import jcifs.smb1.netbios.NbtAddress;
import jcifs.smb1.util.LogStream;
import jcifs.util.Strings;

/**
 * This class represents a resource on an SMB network. Mainly these
 * resources are files and directories however an <code>SmbFile</code>
 * may also refer to servers and workgroups. If the resource is a file or
 * directory the methods of <code>SmbFile</code> follow the behavior of
 * the well known {@link java.io.File} class. One fundamental difference
 * is the usage of a URL scheme [1] to specify the target file or
 * directory. SmbFile URLs have the following syntax:
 *
 * <blockquote><pre>
 *     smb1://[[[domain;]username[:password]@]server[:port]/[[share/[dir/]file]]][?param=value[param2=value2[...]]]
 * </pre></blockquote>
 *
 * This example:
 *
 * <blockquote><pre>
 *     smb1://storage15/public/foo.txt
 * </pre></blockquote>
 *
 * would reference the file <code>foo.txt</code> in the share
 * <code>public</code> on the server <code>storage15</code>. In addition
 * to referencing files and directories, jCIFS can also address servers,
 * and workgroups.
 * <p>
 * <strong><i>Important: all SMB URLs that represent
 * workgroups, servers, shares, or directories require a trailing slash '/'.
 * </i></strong>
 * <p>
 * When using the {@code java.net.URL} class with
 * 'smb1://' URLs it is necessary to first call the static
 * {@code jcifs.smb1.Config.registerSmbURLHandler();} method. This is required
 * to register the SMB protocol handler.
 * <p>
 * The userinfo component of the SMB URL ({@code domain;user:pass}) must
 * be URL encoded if it contains reserved characters. According to RFC 2396
 * these characters are non US-ASCII characters and most meta characters
 * however jCIFS will work correctly with anything but '@' which is used
 * to delimit the userinfo component from the server and '%' which is the
 * URL escape character itself.
 * <p>
 * The server
 * component may a traditional NetBIOS name, a DNS name, or IP
 * address. These name resolution mechanisms and their resolution order
 * can be changed (See <a href="../../../resolver.html">Setting Name
 * Resolution Properties</a>). The servername and path components are
 * not case sensitive but the domain, username, and password components
 * are. It is also likely that properties must be specified for jcifs.smb1
 * to function (See <a href="../../overview-summary.html#scp">Setting
 * JCIFS Properties</a>). Here are some examples of SMB URLs with brief
 * descriptions of what they do:
 *
 * <p>[1] This URL scheme is based largely on the <i>SMB
 * Filesharing URL Scheme</i> IETF draft.
 *
 * <table border="1">
 * <caption>SMB URL Examples</caption>
 * <tr>
 * <td colspan="2"><b>SMB URL Examples</b></td>
 * <tr><td ><b>URL</b></td><td><b>Description</b></td></tr>
 *
 * <tr><td ><code>smb1://users-nyc;miallen:mypass@angus/tmp/</code></td><td>
 * This URL references a share called <code>tmp</code> on the server
 * <code>angus</code> as user <code>miallen</code> who's password is
 * <code>mypass</code>.
 * </td></tr>
 *
 * <tr><td >
 * <code>smb1://Administrator:P%40ss@msmith1/c/WINDOWS/Desktop/foo.txt</code></td><td>
 * A relativly sophisticated example that references a file
 * <code>msmith1</code>'s desktop as user <code>Administrator</code>. Notice the '@' is URL encoded with the '%40' hexcode escape.
 * </td></tr>
 *
 * <tr><td ><code>smb1://angus/</code></td><td>
 * This references only a server. The behavior of some methods is different
 * in this context(e.g. you cannot <code>delete</code> a server) however
 * as you might expect the <code>list</code> method will list the available
 * shares on this server.
 * </td></tr>
 *
 * <tr><td ><code>smb1://myworkgroup/</code></td><td>
 * This syntactically is identical to the above example. However if
 * <code>myworkgroup</code> happends to be a workgroup(which is indeed
 * suggested by the name) the <code>list</code> method will return
 * a list of servers that have registered themselves as members of
 * <code>myworkgroup</code>.
 * </td></tr>
 *
 * <tr><td ><code>smb1://</code></td><td>
 * Just as <code>smb1://server/</code> lists shares and
 * <code>smb1://workgroup/</code> lists servers, the <code>smb1://</code>
 * URL lists all available workgroups on a netbios LAN. Again,
 * in this context many methods are not valid and return default
 * values(e.g. <code>isHidden</code> will always return false).
 * </td></tr>
 *
 * <tr><td ><code>smb1://angus.foo.net/d/jcifs/smb1/pipes.doc</code></td><td>
 * The server name may also be a DNS name as it is in this example. See
 * <a href="../../../resolver.html">Setting Name Resolution Properties</a>
 * for details.
 * </td></tr>
 *
 * <tr><td ><code>smb1://192.168.1.15/ADMIN$/</code></td><td>
 * The server name may also be an IP address. See <a
 * href="../../../resolver.html">Setting Name Resolution Properties</a>
 * for details.
 * </td></tr>
 *
 * <tr><td >
 * <code>smb1://domain;username:password@server/share/path/to/file.txt</code></td><td>
 * A prototypical example that uses all the fields.
 * </td></tr>
 *
 * <tr><td ><code>smb1://myworkgroup/angus/ &lt;-- ILLEGAL </code></td><td>
 * Despite the hierarchial relationship between workgroups, servers, and
 * filesystems this example is not valid.
 * </td></tr>
 *
 * <tr><td >
 * <code>smb1://server/share/path/to/dir &lt;-- ILLEGAL </code></td><td>
 * URLs that represent workgroups, servers, shares, or directories require a trailing slash '/'.
 * </td></tr>
 *
 * <tr><td >
 * <code>smb1://MYGROUP/?SERVER=192.168.10.15</code></td><td>
 * SMB URLs support some query string parameters. In this example
 * the <code>SERVER</code> parameter is used to override the
 * server name service lookup to contact the server 192.168.10.15
 * (presumably known to be a master
 * browser) for the server list in workgroup <code>MYGROUP</code>.
 * </td></tr>
 *
 * </table>
 *
 * <p>A second constructor argument may be specified to augment the URL
 * for better programmatic control when processing many files under
 * a common base. This is slightly different from the corresponding
 * <code>java.io.File</code> usage; a '/' at the beginning of the second
 * parameter will still use the server component of the first parameter. The
 * examples below illustrate the resulting URLs when this second contructor
 * argument is used.
 *
 * <table border="1">
 * <caption>Examples Of SMB URLs When Augmented With A Second Constructor Parameter</caption>
 * <tr>
 * <td colspan="3">
 * <b>Examples Of SMB URLs When Augmented With A Second Constructor Parameter</b></td>
 * <tr><td >
 * <b>First Parameter</b></td><td><b>Second Parameter</b></td><td><b>Result</b></td></tr>
 *
 * <tr><td ><code>
 *  smb1://host/share/a/b/
 * </code></td><td ><code>
 *  c/d/
 * </code></td><td><code>
 *  smb1://host/share/a/b/c/d/
 * </code></td></tr>
 *
 * <tr><td ><code>
 *  smb1://host/share/foo/bar/
 * </code></td><td ><code>
 *  /share2/zig/zag
 * </code></td><td><code>
 *  smb1://host/share2/zig/zag
 * </code></td></tr>
 *
 * <tr><td ><code>
 *  smb1://host/share/foo/bar/
 * </code></td><td ><code>
 *  ../zip/
 * </code></td><td><code>
 *  smb1://host/share/foo/zip/
 * </code></td></tr>
 *
 * <tr><td ><code>
 *  smb1://host/share/zig/zag
 * </code></td><td ><code>
 *  smb1://foo/bar/
 * </code></td><td><code>
 *  smb1://foo/bar/
 * </code></td></tr>
 *
 * <tr><td ><code>
 *  smb1://host/share/foo/
 * </code></td><td ><code>
 *  ../.././.././../foo/
 * </code></td><td><code>
 *  smb1://host/foo/
 * </code></td></tr>
 *
 * <tr><td ><code>
 *  smb1://host/share/zig/zag
 * </code></td><td ><code>
 *  /
 * </code></td><td><code>
 *  smb1://host/
 * </code></td></tr>
 *
 * <tr><td ><code>
 *  smb1://server/
 * </code></td><td ><code>
 *  ../
 * </code></td><td><code>
 *  smb1://server/
 * </code></td></tr>
 *
 * <tr><td ><code>
 *  smb1://
 * </code></td><td ><code>
 *  myworkgroup/
 * </code></td><td><code>
 *  smb1://myworkgroup/
 * </code></td></tr>
 *
 * <tr><td ><code>
 *  smb1://myworkgroup/
 * </code></td><td ><code>
 *  angus/
 * </code></td><td><code>
 *  smb1://myworkgroup/angus/ &lt;-- ILLEGAL<br>(But if you first create an SmbFile with 'smb1://workgroup/' and use it as the first parameter to a constructor that accepts it with a second String parameter jCIFS will factor out the 'workgroup'.)
 * </code></td></tr>
 *
 * </table>
 *
 * <p>Instances of the <code>SmbFile</code> class are immutable; that is,
 * once created, the abstract pathname represented by an SmbFile object
 * will never change.
 *
 * @see       java.io.File
 */

public class SmbFile extends URLConnection implements SmbConstants {

    static final int O_RDONLY = 0x01;
    static final int O_WRONLY = 0x02;
    static final int O_RDWR = 0x03;
    static final int O_APPEND = 0x04;

    // Open Function Encoding
    // create if the file does not exist
    static final int O_CREAT = 0x0010;
    // fail if the file exists
    static final int O_EXCL = 0x0020;
    // truncate if the file exists
    static final int O_TRUNC = 0x0040;

    // share access
    /**
     * When specified as the {@code shareAccess} constructor parameter,
     * other SMB clients (including other threads making calls into jCIFS)
     * will not be permitted to access the target file and will receive "The
     * file is being accessed by another process" message.
     */
    public static final int FILE_NO_SHARE = 0x00;
    /**
     * When specified as the {@code shareAccess} constructor parameter,
     * other SMB clients will be permitted to read from the target file while
     * this file is open. This constant may be logically OR'd with other share
     * access flags.
     */
    public static final int FILE_SHARE_READ = 0x01;
    /**
     * When specified as the {@code shareAccess} constructor parameter,
     * other SMB clients will be permitted to write to the target file while
     * this file is open. This constant may be logically OR'd with other share
     * access flags.
     */
    public static final int FILE_SHARE_WRITE = 0x02;
    /**
     * When specified as the {@code shareAccess} constructor parameter,
     * other SMB clients will be permitted to delete the target file while
     * this file is open. This constant may be logically OR'd with other share
     * access flags.
     */
    public static final int FILE_SHARE_DELETE = 0x04;

    // file attribute encoding
    /**
     * A file with this bit on as returned by {@code getAttributes()} or set
     * with {@code setAttributes()} will be read-only
     */
    public static final int ATTR_READONLY = 0x01;
    /**
     * A file with this bit on as returned by {@code getAttributes()} or set
     * with {@code setAttributes()} will be hidden
     */
    public static final int ATTR_HIDDEN = 0x02;
    /**
     * A file with this bit on as returned by {@code getAttributes()} or set
     * with {@code setAttributes()} will be a system file
     */
    public static final int ATTR_SYSTEM = 0x04;
    /**
     * A file with this bit on as returned by {@code getAttributes()} is
     * a volume
     */
    public static final int ATTR_VOLUME = 0x08;
    /**
     * A file with this bit on as returned by {@code getAttributes()} is
     * a directory
     */
    public static final int ATTR_DIRECTORY = 0x10;
    /**
     * A file with this bit on as returned by {@code getAttributes()} or set
     * with {@code setAttributes()} is an archived file
     */
    public static final int ATTR_ARCHIVE = 0x20;

    // extended file attribute encoding(others same as above)
    static final int ATTR_COMPRESSED = 0x800;
    static final int ATTR_NORMAL = 0x080;
    static final int ATTR_TEMPORARY = 0x100;

    static final int ATTR_GET_MASK = 0x7FFF; /* orig 0x7fff */
    static final int ATTR_SET_MASK = 0x30A7; /* orig 0x0027 */

    static final int DEFAULT_ATTR_EXPIRATION_PERIOD = 5000;

    static final int HASH_DOT = ".".hashCode();
    static final int HASH_DOT_DOT = "..".hashCode();

    static LogStream log = LogStream.getInstance();
    private static final Logger logger = LoggerFactory.getLogger(SmbFile.class);
    static long attrExpirationPeriod;
    static boolean ignoreCopyToException;

    static {

        try {
            Class.forName("jcifs.smb1.Config");
        } catch (final ClassNotFoundException cnfe) {
            logger.error("Failed to load jcifs.smb1.Config class", cnfe);
        }
        attrExpirationPeriod = Config.getLong("jcifs.smb1.smb.client.attrExpirationPeriod", DEFAULT_ATTR_EXPIRATION_PERIOD);
        ignoreCopyToException = Config.getBoolean("jcifs.smb1.smb.client.ignoreCopyToException", true);
        dfs = new Dfs();
    }

    /**
     * Returned by {@link #getType()} if the resource this {@code SmbFile}
     * represents is a regular file or directory.
     */
    public static final int TYPE_FILESYSTEM = 0x01;
    /**
     * Returned by {@link #getType()} if the resource this {@code SmbFile}
     * represents is a workgroup.
     */
    public static final int TYPE_WORKGROUP = 0x02;
    /**
     * Returned by {@link #getType()} if the resource this {@code SmbFile}
     * represents is a server.
     */
    public static final int TYPE_SERVER = 0x04;
    /**
     * Returned by {@link #getType()} if the resource this {@code SmbFile}
     * represents is a share.
     */
    public static final int TYPE_SHARE = 0x08;
    /**
     * Returned by {@link #getType()} if the resource this {@code SmbFile}
     * represents is a named pipe.
     */
    public static final int TYPE_NAMED_PIPE = 0x10;
    /**
     * Returned by {@link #getType()} if the resource this {@code SmbFile}
     * represents is a printer.
     */
    public static final int TYPE_PRINTER = 0x20;
    /**
     * Returned by {@link #getType()} if the resource this {@code SmbFile}
     * represents is a communications device.
     */
    public static final int TYPE_COMM = 0x40;

    private String canon; // Initially null; set by getUncPath; dir must end with '/'
    private String share; // Can be null
    private long createTime;
    private long lastModified;
    private int attributes;
    private long attrExpiration;
    private long size;
    private long sizeExpiration;
    private boolean isExists;
    private int shareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
    private SmbComBlankResponse blank_resp = null;
    private DfsReferral dfsReferral = null; // For getDfsPath() and getServerWithDfs()

    /**
     * DFS resolver instance
     */
    protected static Dfs dfs;

    NtlmPasswordAuthentication auth; // Cannot be null
    SmbTree tree = null; // Initially null
    String unc; // Initially null; set by getUncPath; never ends with '/'
    int fid; // Initially 0; set by open()
    int type;
    boolean opened;
    int tree_num;

    /**
     * Constructs an SmbFile representing a resource on an SMB network such as
     * a file or directory. See the description and examples of smb URLs above.
     *
     * @param   url A URL string
     * @throws  MalformedURLException
     *          If the <code>parent</code> and <code>child</code> parameters
     *          do not follow the prescribed syntax
     */

    public SmbFile(final String url) throws MalformedURLException {
        this(new URL(null, url, Handler.SMB_HANDLER));
    }

    /**
     * Constructs an SmbFile representing a resource on an SMB network such
     * as a file or directory. The second parameter is a relative path from
     * the <code>parent SmbFile</code>. See the description above for examples
     * of using the second <code>name</code> parameter.
     *
     * @param   context A base <code>SmbFile</code>
     * @param   name A path string relative to the <code>parent</code> paremeter
     * @throws  MalformedURLException
     *          If the <code>parent</code> and <code>child</code> parameters
     *          do not follow the prescribed syntax
     * @throws  UnknownHostException
     *          If the server or workgroup of the {@code context} file cannot be determined
     */

    public SmbFile(final SmbFile context, final String name) throws MalformedURLException, UnknownHostException {
        this(context.isWorkgroup0() ? new URL(null, "smb1://" + name, Handler.SMB_HANDLER)
                : new URL(context.url, name, Handler.SMB_HANDLER), context.auth);
    }

    /**
     * Constructs an SmbFile representing a resource on an SMB network such
     * as a file or directory. The second parameter is a relative path from
     * the <code>parent</code>. See the description above for examples of
     * using the second <code>chile</code> parameter.
     *
     * @param   context A URL string
     * @param   name A path string relative to the <code>context</code> paremeter
     * @throws  MalformedURLException
     *          If the <code>context</code> and <code>name</code> parameters
     *          do not follow the prescribed syntax
     */

    public SmbFile(final String context, final String name) throws MalformedURLException {
        this(new URL(new URL(null, context, Handler.SMB_HANDLER), name, Handler.SMB_HANDLER));
    }

    /**
     * Constructs an SmbFile representing a resource on an SMB network such
     * as a file or directory.
     *
     * @param   url A URL string
     * @param   auth The credentials the client should use for authentication
     * @throws  MalformedURLException
     *          If the <code>url</code> parameter does not follow the prescribed syntax
     */
    public SmbFile(final String url, final NtlmPasswordAuthentication auth) throws MalformedURLException {
        this(new URL(null, url, Handler.SMB_HANDLER), auth);
    }

    /**
     * Constructs an SmbFile representing a file on an SMB network. The
     * {@code shareAccess} parameter controls what permissions other
     * clients have when trying to access the same file while this instance
     * is still open. This value is either {@code FILE_NO_SHARE} or any
     * combination of {@code FILE_SHARE_READ}, {@code FILE_SHARE_WRITE},
     * and {@code FILE_SHARE_DELETE} logically OR'd together.
     *
     * @param   url A URL string
     * @param   auth The credentials the client should use for authentication
     * @param   shareAccess Specifies what access other clients have while this file is open.
     * @throws  MalformedURLException
     *          If the <code>url</code> parameter does not follow the prescribed syntax
     */
    public SmbFile(final String url, final NtlmPasswordAuthentication auth, final int shareAccess) throws MalformedURLException {
        this(new URL(null, url, Handler.SMB_HANDLER), auth);
        if ((shareAccess & ~(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)) != 0) {
            throw new RuntimeException("Illegal shareAccess parameter");
        }
        this.shareAccess = shareAccess;
    }

    /**
     * Constructs an SmbFile representing a resource on an SMB network such
     * as a file or directory. The second parameter is a relative path from
     * the <code>context</code>. See the description above for examples of
     * using the second <code>name</code> parameter.
     *
     * @param   context A URL string
     * @param   name A path string relative to the <code>context</code> paremeter
     * @param   auth The credentials the client should use for authentication
     * @throws  MalformedURLException
     *          If the <code>context</code> and <code>name</code> parameters
     *          do not follow the prescribed syntax
     */
    public SmbFile(final String context, final String name, final NtlmPasswordAuthentication auth) throws MalformedURLException {
        this(new URL(new URL(null, context, Handler.SMB_HANDLER), name, Handler.SMB_HANDLER), auth);
    }

    /**
     * Constructs an SmbFile representing a resource on an SMB network such
     * as a file or directory. The second parameter is a relative path from
     * the <code>context</code>. See the description above for examples of
     * using the second <code>name</code> parameter. The {@code shareAccess}
     * parameter controls what permissions other clients have when trying
     * to access the same file while this instance is still open. This
     * value is either {@code FILE_NO_SHARE} or any combination
     * of {@code FILE_SHARE_READ}, {@code FILE_SHARE_WRITE}, and
     * {@code FILE_SHARE_DELETE} logically OR'd together.
     *
     * @param   context A URL string
     * @param   name A path string relative to the <code>context</code> paremeter
     * @param   auth The credentials the client should use for authentication
     * @param   shareAccess Specifies what access other clients have while this file is open.
     * @throws  MalformedURLException
     *          If the <code>context</code> and <code>name</code> parameters
     *          do not follow the prescribed syntax
     */
    public SmbFile(final String context, final String name, final NtlmPasswordAuthentication auth, final int shareAccess)
            throws MalformedURLException {
        this(new URL(new URL(null, context, Handler.SMB_HANDLER), name, Handler.SMB_HANDLER), auth);
        if ((shareAccess & ~(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)) != 0) {
            throw new RuntimeException("Illegal shareAccess parameter");
        }
        this.shareAccess = shareAccess;
    }

    /**
     * Constructs an SmbFile representing a resource on an SMB network such
     * as a file or directory. The second parameter is a relative path from
     * the <code>context</code>. See the description above for examples of
     * using the second <code>name</code> parameter. The {@code shareAccess}
     * parameter controls what permissions other clients have when trying
     * to access the same file while this instance is still open. This
     * value is either {@code FILE_NO_SHARE} or any combination
     * of {@code FILE_SHARE_READ}, {@code FILE_SHARE_WRITE}, and
     * {@code FILE_SHARE_DELETE} logically OR'd together.
     *
     * @param   context A base <code>SmbFile</code>
     * @param   name A path string relative to the <code>context</code> file path
     * @param   shareAccess Specifies what access other clients have while this file is open.
     * @throws  MalformedURLException
     *          If the <code>context</code> and <code>name</code> parameters
     *          do not follow the prescribed syntax
     * @throws  UnknownHostException if the server cannot be resolved
     */
    public SmbFile(final SmbFile context, final String name, final int shareAccess) throws MalformedURLException, UnknownHostException {
        this(context.isWorkgroup0() ? new URL(null, "smb1://" + name, Handler.SMB_HANDLER)
                : new URL(context.url, name, Handler.SMB_HANDLER), context.auth);
        if ((shareAccess & ~(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)) != 0) {
            throw new RuntimeException("Illegal shareAccess parameter");
        }
        this.shareAccess = shareAccess;
    }

    /**
     * Constructs an SmbFile representing a resource on an SMB network such
     * as a file or directory from a {@code URL} object.
     *
     * @param   url The URL of the target resource
     */
    public SmbFile(final URL url) {
        this(url, new NtlmPasswordAuthentication(url.getUserInfo()));
    }

    /**
     * Constructs an SmbFile representing a resource on an SMB network such
     * as a file or directory from a {@code URL} object and an
     * {@code NtlmPasswordAuthentication} object.
     *
     * @param   url The URL of the target resource
     * @param   auth The credentials the client should use for authentication
     */
    public SmbFile(final URL url, final NtlmPasswordAuthentication auth) {
        super(url);
        this.auth = auth == null ? new NtlmPasswordAuthentication(url.getUserInfo()) : auth;

        getUncPath0();
    }

    SmbFile(final SmbFile context, String name, final int type, final int attributes, final long createTime, final long lastModified,
            final long size) throws MalformedURLException, UnknownHostException {
        this(context.isWorkgroup0() ? new URL(null, "smb1://" + name + "/", Handler.SMB_HANDLER)
                : new URL(context.url, name + ((attributes & ATTR_DIRECTORY) > 0 ? "/" : "")));

        /* why was this removed before? DFS? copyTo? Am I going around in circles? */
        auth = context.auth;

        if (context.share != null) {
            this.tree = context.tree;
            this.dfsReferral = context.dfsReferral;
        }
        final int last = name.length() - 1;
        if (name.charAt(last) == '/') {
            name = name.substring(0, last);
        }
        if (context.share == null) {
            this.unc = "\\";
        } else if (context.unc.equals("\\")) {
            this.unc = '\\' + name;
        } else {
            this.unc = context.unc + '\\' + name;
        }
        /* why? am I going around in circles?
         *  this.type = type == TYPE_WORKGROUP ? 0 : type;
         */
        this.type = type;
        this.attributes = attributes;
        this.createTime = createTime;
        this.lastModified = lastModified;
        this.size = size;
        isExists = true;

        attrExpiration = sizeExpiration = System.currentTimeMillis() + attrExpirationPeriod;
    }

    private SmbComBlankResponse blank_resp() {
        if (blank_resp == null) {
            blank_resp = new SmbComBlankResponse();
        }
        return blank_resp;
    }

    void resolveDfs(final ServerMessageBlock request) throws SmbException {
        if (request instanceof SmbComClose) {
            return;
        }

        connect0();

        DfsReferral dr = dfs.resolve(tree.session.transport.tconHostName, tree.share, unc, auth);
        if (dr != null) {
            String service = null;

            if (request != null) {
                switch (request.command) {
                case ServerMessageBlock.SMB_COM_TRANSACTION:
                case ServerMessageBlock.SMB_COM_TRANSACTION2:
                    switch (((SmbComTransaction) request).subCommand & 0xFF) {
                    case SmbComTransaction.TRANS2_GET_DFS_REFERRAL:
                        break;
                    default:
                        service = "A:";
                    }
                    break;
                default:
                    service = "A:";
                }
            }

            final DfsReferral start = dr;
            SmbException se = null;

            do {
                try {
                    if (LogStream.level >= 2) {
                        log.println("DFS redirect: " + dr);
                    }

                    final UniAddress addr = UniAddress.getByName(dr.server);
                    final SmbTransport trans = SmbTransport.getSmbTransport(addr, url.getPort());
                    /* This is a key point. This is where we set the "tree" of this file which
                     * is like changing the rug out from underneath our feet.
                     */
                    /* Technically we should also try to authenticate here but that means doing the session setup and tree connect separately. For now a simple connect will at least tell us if the host is alive. That should be sufficient for 99% of the cases. We can revisit this again for 2.0.
                     */
                    trans.connect();
                    tree = trans.getSmbSession(auth).getSmbTree(dr.share, service);

                    if (dr != start && dr.key != null) {
                        dr.map.put(dr.key, dr);
                    }

                    se = null;

                    break;
                } catch (final IOException ioe) {
                    if (ioe instanceof SmbException) {
                        se = (SmbException) ioe;
                    } else {
                        se = new SmbException(dr.server, ioe);
                    }
                }

                dr = dr.next;
            } while (dr != start);

            if (se != null) {
                throw se;
            }

            if (LogStream.level >= 3) {
                log.println(dr);
            }

            dfsReferral = dr;
            if (dr.pathConsumed < 0) {
                dr.pathConsumed = 0;
            } else if (dr.pathConsumed > unc.length()) {
                dr.pathConsumed = unc.length();
            }
            String dunc = unc.substring(dr.pathConsumed);
            if (dunc.equals("")) {
                dunc = "\\";
            }
            if (!dr.path.equals("")) {
                dunc = "\\" + dr.path + dunc;
            }

            unc = dunc;
            if (request != null && request.path != null && request.path.endsWith("\\") && !dunc.endsWith("\\")) {
                dunc += "\\";
            }
            if (request != null) {
                request.path = dunc;
                request.flags2 |= SmbConstants.FLAGS2_RESOLVE_PATHS_IN_DFS;
            }
        } else if (tree.inDomainDfs && !(request instanceof NtTransQuerySecurityDesc) && !(request instanceof SmbComClose)
                && !(request instanceof SmbComFindClose2)) {
            throw new SmbException(NtStatus.NT_STATUS_NOT_FOUND, false);
        } else if (request != null) {
            request.flags2 &= ~SmbConstants.FLAGS2_RESOLVE_PATHS_IN_DFS;
        }
    }

    void send(final ServerMessageBlock request, final ServerMessageBlock response) throws SmbException {
        for (;;) {
            resolveDfs(request);
            try {
                tree.send(request, response);
                break;
            } catch (final DfsReferral dre) {
                if (dre.resolveHashes) {
                    throw dre;
                }
                request.reset();
            }
        }
    }

    static String queryLookup(final String query, final String param) {
        final char in[] = query.toCharArray();
        int i, ch, st, eq;

        st = eq = 0;
        for (i = 0; i < in.length; i++) {
            ch = in[i];
            if (ch == '&') {
                if (eq > st) {
                    final String p = new String(in, st, eq - st);
                    if (p.equalsIgnoreCase(param)) {
                        eq++;
                        return new String(in, eq, i - eq);
                    }
                }
                st = i + 1;
            } else if (ch == '=') {
                eq = i;
            }
        }
        if (eq > st) {
            final String p = new String(in, st, eq - st);
            if (p.equalsIgnoreCase(param)) {
                eq++;
                return new String(in, eq, in.length - eq);
            }
        }

        return null;
    }

    UniAddress[] addresses;
    int addressIndex;

    UniAddress getAddress() throws UnknownHostException {
        if (addressIndex == 0) {
            return getFirstAddress();
        }
        return addresses[addressIndex - 1];
    }

    UniAddress getFirstAddress() throws UnknownHostException {
        addressIndex = 0;

        final String host = url.getHost();
        final String path = url.getPath();
        final String query = url.getQuery();

        if (query != null) {
            final String server = queryLookup(query, "server");
            if (server != null && server.length() > 0) {
                addresses = new UniAddress[1];
                addresses[0] = UniAddress.getByName(server);
                return getNextAddress();
            }
            final String address = queryLookup(query, "address");
            if (address != null && address.length() > 0) {
                final byte[] ip = java.net.InetAddress.getByName(address).getAddress();
                addresses = new UniAddress[1];
                addresses[0] = new UniAddress(java.net.InetAddress.getByAddress(host, ip));
                return getNextAddress();
            }
        }

        if (host.length() == 0) {
            try {
                final NbtAddress addr = NbtAddress.getByName(NbtAddress.MASTER_BROWSER_NAME, 0x01, null);
                addresses = new UniAddress[1];
                addresses[0] = UniAddress.getByName(addr.getHostAddress());
            } catch (final UnknownHostException uhe) {
                NtlmPasswordAuthentication.initDefaults();
                if (NtlmPasswordAuthentication.DEFAULT_DOMAIN.equals("?")) {
                    throw uhe;
                }
                addresses = UniAddress.getAllByName(NtlmPasswordAuthentication.DEFAULT_DOMAIN, true);
            }
        } else if (path.length() == 0 || path.equals("/")) {
            addresses = UniAddress.getAllByName(host, true);
        } else {
            addresses = UniAddress.getAllByName(host, false);
        }

        return getNextAddress();
    }

    UniAddress getNextAddress() {
        UniAddress addr = null;
        if (addressIndex < addresses.length) {
            addr = addresses[addressIndex++];
        }
        return addr;
    }

    boolean hasNextAddress() {
        return addressIndex < addresses.length;
    }

    void connect0() throws SmbException {
        try {
            connect();
        } catch (final SmbException se) {
            throw se;
        } catch (final IOException ioe) {
            throw new SmbException("Failed to connect to server", ioe);
        }
    }

    void doConnect() throws IOException {
        SmbTransport trans;
        UniAddress addr = getAddress();

        if (tree != null) {
            trans = tree.session.transport;
        } else {
            trans = SmbTransport.getSmbTransport(addr, url.getPort());
            tree = trans.getSmbSession(auth).getSmbTree(share, null);
        }

        final String hostName = getServerWithDfs();
        tree.inDomainDfs = dfs.resolve(hostName, tree.share, null, auth) != null;
        if (tree.inDomainDfs) {
            tree.connectionState = 2;
        }

        try {
            if (LogStream.level >= 3) {
                log.println("doConnect: " + addr);
            }

            tree.treeConnect(null, null);
        } catch (final SmbAuthException sae) {
            NtlmPasswordAuthentication a;
            SmbSession ssn;

            if (share == null) { // IPC$ - try "anonymous" credentials
                ssn = trans.getSmbSession(NtlmPasswordAuthentication.NULL);
                tree = ssn.getSmbTree(null, null);
            } else if ((a = NtlmAuthenticator.requestNtlmPasswordAuthentication(url.toString(), sae)) != null) {
                auth = a;
                ssn = trans.getSmbSession(auth);
                tree = ssn.getSmbTree(share, null);
                tree.inDomainDfs = dfs.resolve(hostName, tree.share, null, auth) != null;
                if (tree.inDomainDfs) {
                    tree.connectionState = 2;
                }
            } else {
                if (LogStream.level >= 1 && hasNextAddress()) {
                    sae.printStackTrace(log);
                }
                throw sae;
            }
            tree.treeConnect(null, null);
        }
    }

    /**
     * It is not necessary to call this method directly. This is the
     * {@code URLConnection} implementation of {@code connect()}.
     */
    @Override
    public void connect() throws IOException {
        if (isConnected() && tree.session.transport.tconHostName == null) {
            /* Tree thinks it is connected but transport disconnected
             * under it, reset tree to reflect the truth.
             */
            tree.treeDisconnect(true);
        }

        if (isConnected()) {
            return;
        }

        getUncPath0();
        getFirstAddress();
        for (;;) {
            try {
                doConnect();
                return;
            } catch (final SmbAuthException sae) {
                throw sae; // Prevents account lockout on servers with multiple IPs
            } catch (final SmbException se) {
                if (getNextAddress() == null) {
                    throw se;
                }
                if (LogStream.level >= 3) {
                    se.printStackTrace(log);
                }
            }
        }
    }

    boolean isConnected() {
        return tree != null && tree.connectionState == 2;
    }

    int open0(final int flags, final int access, final int attrs, final int options) throws SmbException {
        int f;

        connect0();

        if (LogStream.level >= 3) {
            log.println("open0: " + unc);
        }

        /*
         * NT Create AndX / Open AndX Request / Response
         */

        if (tree.session.transport.hasCapability(SmbConstants.CAP_NT_SMBS)) {
            final SmbComNTCreateAndXResponse response = new SmbComNTCreateAndXResponse();
            final SmbComNTCreateAndX request = new SmbComNTCreateAndX(unc, flags, access, shareAccess, attrs, options, null);
            if (this instanceof SmbNamedPipe) {
                request.flags0 |= 0x16;
                request.desiredAccess |= 0x20000;
                response.isExtended = true;
            }
            send(request, response);
            f = response.fid;
            attributes = response.extFileAttributes & ATTR_GET_MASK;
            attrExpiration = System.currentTimeMillis() + attrExpirationPeriod;
            isExists = true;
        } else {
            final SmbComOpenAndXResponse response = new SmbComOpenAndXResponse();
            send(new SmbComOpenAndX(unc, access, flags, null), response);
            f = response.fid;
        }

        return f;
    }

    void open(final int flags, final int access, final int attrs, final int options) throws SmbException {
        if (isOpen()) {
            return;
        }
        fid = open0(flags, access, attrs, options);
        opened = true;
        tree_num = tree.tree_num;
    }

    boolean isOpen() {
        return opened && isConnected() && tree_num == tree.tree_num;
    }

    void close(final int f, final long lastWriteTime) throws SmbException {

        if (LogStream.level >= 3) {
            log.println("close: " + f);
        }

        /*
         * Close Request / Response
         */

        send(new SmbComClose(f, lastWriteTime), blank_resp());
    }

    void close(final long lastWriteTime) throws SmbException {
        if (!isOpen()) {
            return;
        }
        close(fid, lastWriteTime);
        opened = false;
    }

    void close() throws SmbException {
        close(0L);
    }

    /**
     * Returns the {@code NtlmPasswordAuthentication} object used as
     * credentials with this file or pipe. This can be used to retrieve the
     * username for example:
     * {@code
     * String username = f.getPrincipal().getName();
     * }
     * The {@code Principal} object returned will never be {@code null}
     * however the username can be {@code null} indication anonymous
     * credentials were used (e.g. some IPC$ services).
     */

    /**
     * Returns the principal used for authentication
     *
     * @return the authentication principal
     */
    public Principal getPrincipal() {
        return auth;
    }

    /**
     * Returns the last component of the target URL. This will
     * effectively be the name of the file or directory represented by this
     * <code>SmbFile</code> or in the case of URLs that only specify a server
     * or workgroup, the server or workgroup will be returned. The name of
     * the root URL <code>smb1://</code> is also <code>smb1://</code>. If this
     * {@code SmbFile} refers to a workgroup, server, share, or directory,
     * the name will include a trailing slash '/' so that composing new
     * {@code SmbFile}s will maintain the trailing slash requirement.
     *
     * @return  The last component of the URL associated with this SMB
     *          resource or <code>smb1://</code> if the resource is <code>smb1://</code>
     *          itself.
     */

    public String getName() {
        getUncPath0();
        if (canon.length() > 1) {
            int i = canon.length() - 2;
            while (canon.charAt(i) != '/') {
                i--;
            }
            return canon.substring(i + 1);
        }
        if (share != null) {
            return share + '/';
        }
        if (url.getHost().length() > 0) {
            return url.getHost() + '/';
        }
        return "smb1://";
    }

    /**
     * Everything but the last component of the URL representing this SMB
     * resource is effectivly it's parent. The root URL <code>smb1://</code>
     * does not have a parent. In this case <code>smb1://</code> is returned.
     *
     * @return   The parent directory of this SMB resource or
     *           <code>smb1://</code> if the resource refers to the root of the URL
     *           hierarchy which incedentally is also <code>smb1://</code>.
     */

    public String getParent() {
        String str = url.getAuthority();

        if (str.length() > 0) {
            final StringBuilder sb = new StringBuilder("smb1://");

            sb.append(str);

            getUncPath0();
            if (canon.length() > 1) {
                sb.append(canon);
            } else {
                sb.append('/');
            }

            str = sb.toString();

            int i = str.length() - 2;
            while (str.charAt(i) != '/') {
                i--;
            }

            return str.substring(0, i + 1);
        }

        return "smb1://";
    }

    /**
     * Returns the full uncanonicalized URL of this SMB resource. An
     * <code>SmbFile</code> constructed with the result of this method will
     * result in an <code>SmbFile</code> that is equal to the original.
     *
     * @return  The uncanonicalized full URL of this SMB resource.
     */

    public String getPath() {
        return url.toString();
    }

    String getUncPath0() {
        if (unc == null) {
            final char[] in = url.getPath().toCharArray();
            final char[] out = new char[in.length];
            final int length = in.length;
            int i, o, state;
            final int s;

            /* The canonicalization routine
             */
            state = 0;
            o = 0;
            for (i = 0; i < length; i++) {
                switch (state) {
                case 0:
                    if (in[i] != '/') {
                        return null;
                    }
                    out[o] = in[i];
                    o++;
                    state = 1;
                    break;
                case 1:
                    if (in[i] == '/') {
                        break;
                    }
                    if (in[i] == '.' && (i + 1 >= length || in[i + 1] == '/')) {
                        i++;
                        break;
                    } else if (i + 1 < length && in[i] == '.' && in[i + 1] == '.' && (i + 2 >= length || in[i + 2] == '/')) {
                        i += 2;
                        if (o == 1) {
                            break;
                        }
                        do {
                            o--;
                        } while (o > 1 && out[o - 1] != '/');
                        break;
                    }
                    state = 2;
                case 2:
                    if (in[i] == '/') {
                        state = 1;
                    }
                    out[o++] = in[i];
                    break;
                }
            }

            canon = new String(out, 0, o);

            if (o > 1) {
                o--;
                i = canon.indexOf('/', 1);
                if (i < 0) {
                    share = canon.substring(1);
                    unc = "\\";
                } else if (i == o) {
                    share = canon.substring(1, i);
                    unc = "\\";
                } else {
                    share = canon.substring(1, i);
                    unc = canon.substring(i, out[o] == '/' ? o : o + 1);
                    unc = unc.replace('/', '\\');
                }
            } else {
                share = null;
                unc = "\\";
            }
        }
        return unc;
    }

    /**
     * Retuns the Windows UNC style path with backslashs intead of forward slashes.
     *
     * @return  The UNC path.
     */
    public String getUncPath() {
        getUncPath0();
        if (share == null) {
            return "\\\\" + url.getHost();
        }
        return "\\\\" + url.getHost() + canon.replace('/', '\\');
    }

    /**
     * Returns the full URL of this SMB resource with '.' and '..' components
     * factored out. An <code>SmbFile</code> constructed with the result of
     * this method will result in an <code>SmbFile</code> that is equal to
     * the original.
     *
     * @return  The canonicalized URL of this SMB resource.
     */

    public String getCanonicalPath() {
        final String str = url.getAuthority();
        getUncPath0();
        if (str.length() > 0) {
            return "smb1://" + url.getAuthority() + canon;
        }
        return "smb1://";
    }

    /**
     * Retrieves the share associated with this SMB resource. In
     * the case of <code>smb1://</code>, <code>smb1://workgroup/</code>,
     * and <code>smb1://server/</code> URLs which do not specify a share,
     * <code>null</code> will be returned.
     *
     * @return  The share component or <code>null</code> if there is no share
     */

    public String getShare() {
        return share;
    }

    String getServerWithDfs() {
        if (dfsReferral != null) {
            return dfsReferral.server;
        }
        return getServer();
    }

    /**
     * Retrieve the hostname of the server for this SMB resource. If this
     * <code>SmbFile</code> references a workgroup, the name of the workgroup
     * is returned. If this <code>SmbFile</code> refers to the root of this
     * SMB network hierarchy, <code>null</code> is returned.
     *
     * @return  The server or workgroup name or <code>null</code> if this
     *          <code>SmbFile</code> refers to the root <code>smb1://</code> resource.
     */

    public String getServer() {
        final String str = url.getHost();
        if (str.length() == 0) {
            return null;
        }
        return str;
    }

    /**
     * Returns type of of object this {@code SmbFile} represents.
     * @return {@code TYPE_FILESYSTEM, TYPE_WORKGROUP, TYPE_SERVER, TYPE_SHARE,
     * TYPE_PRINTER, TYPE_NAMED_PIPE}, or {@code TYPE_COMM}.
     * @throws SmbException if an error occurs while determining the type
     */
    public int getType() throws SmbException {
        if (type == 0) {
            if (getUncPath0().length() > 1) {
                type = TYPE_FILESYSTEM;
            } else if (share != null) {
                // treeConnect good enough to test service type
                connect0();
                if (share.equals("IPC$")) {
                    type = TYPE_NAMED_PIPE;
                } else if (tree.service.equals("LPT1:")) {
                    type = TYPE_PRINTER;
                } else if (tree.service.equals("COMM")) {
                    type = TYPE_COMM;
                } else {
                    type = TYPE_SHARE;
                }
            } else if (url.getAuthority() == null || url.getAuthority().length() == 0) {
                type = TYPE_WORKGROUP;
            } else {
                UniAddress addr;
                try {
                    addr = getAddress();
                } catch (final UnknownHostException uhe) {
                    throw new SmbException(url.toString(), uhe);
                }
                if (addr.getAddress() instanceof NbtAddress) {
                    final int code = ((NbtAddress) addr.getAddress()).getNameType();
                    if (code == 0x1d || code == 0x1b) {
                        type = TYPE_WORKGROUP;
                        return type;
                    }
                }
                type = TYPE_SERVER;
            }
        }
        return type;
    }

    boolean isWorkgroup0() throws UnknownHostException {
        if (type == TYPE_WORKGROUP || url.getHost().length() == 0) {
            type = TYPE_WORKGROUP;
            return true;
        }
        getUncPath0();
        if (share == null) {
            final UniAddress addr = getAddress();
            if (addr.getAddress() instanceof NbtAddress) {
                final int code = ((NbtAddress) addr.getAddress()).getNameType();
                if (code == 0x1d || code == 0x1b) {
                    type = TYPE_WORKGROUP;
                    return true;
                }
            }
            type = TYPE_SERVER;
        }
        return false;
    }

    Info queryPath(final String path, final int infoLevel) throws SmbException {
        connect0();

        if (LogStream.level >= 3) {
            log.println("queryPath: " + path);
        }

        /* normally we'd check the negotiatedCapabilities for CAP_NT_SMBS
         * however I can't seem to get a good last modified time from
         * SMB_COM_QUERY_INFORMATION so if NT_SMBs are requested
         * by the server than in this case that's what it will get
         * regardless of what jcifs.smb1.smb1.client.useNTSmbs is set
         * to(overrides negotiatedCapabilities).
         */

        /* We really should do the referral before this in case
         * the redirected target has different capabilities. But
         * the way we have been doing that is to call exists() which
         * calls this method so another technique will be necessary
         * to support DFS referral _to_ Win95/98/ME.
         */

        if (tree.session.transport.hasCapability(SmbConstants.CAP_NT_SMBS)) {

            /*
             * Trans2 Query Path Information Request / Response
             */

            final Trans2QueryPathInformationResponse response = new Trans2QueryPathInformationResponse(infoLevel);
            send(new Trans2QueryPathInformation(path, infoLevel), response);

            return response.info;
        }
        final SmbComQueryInformationResponse response =
                new SmbComQueryInformationResponse(tree.session.transport.server.serverTimeZone * 1000 * 60L);
        send(new SmbComQueryInformation(path), response);
        return response;
    }

    /**
     * Tests to see if the SMB resource exists. If the resource refers
     * only to a server, this method determines if the server exists on the
     * network and is advertising SMB services. If this resource refers to
     * a workgroup, this method determines if the workgroup name is valid on
     * the local SMB network. If this <code>SmbFile</code> refers to the root
     * <code>smb1://</code> resource <code>true</code> is always returned. If
     * this <code>SmbFile</code> is a traditional file or directory, it will
     * be queried for on the specified server as expected.
     *
     * @return <code>true</code> if the resource exists or is alive or
     *         <code>false</code> otherwise
     * @throws SmbException if an error occurs while checking existence
     */

    public boolean exists() throws SmbException {

        if (attrExpiration > System.currentTimeMillis()) {
            return isExists;
        }

        attributes = ATTR_READONLY | ATTR_DIRECTORY;
        createTime = 0L;
        lastModified = 0L;
        isExists = false;

        try {
            if (url.getHost().length() == 0) {} else if (share == null) {
                if (getType() == TYPE_WORKGROUP) {
                    UniAddress.getByName(url.getHost(), true);
                } else {
                    UniAddress.getByName(url.getHost()).getHostName();
                }
            } else if (getUncPath0().length() == 1 || share.equalsIgnoreCase("IPC$")) {
                connect0(); // treeConnect is good enough
            } else {
                final Info info = queryPath(getUncPath0(), Trans2QueryPathInformationResponse.SMB_QUERY_FILE_BASIC_INFO);
                attributes = info.getAttributes();
                createTime = info.getCreateTime();
                lastModified = info.getLastWriteTime();
            }

            /* If any of the above fail, isExists will not be set true
             */

            isExists = true;

        } catch (final UnknownHostException uhe) {} catch (final SmbException se) {
            switch (se.getNtStatus()) {
            case NtStatus.NT_STATUS_NO_SUCH_FILE:
            case NtStatus.NT_STATUS_OBJECT_NAME_INVALID:
            case NtStatus.NT_STATUS_OBJECT_NAME_NOT_FOUND:
            case NtStatus.NT_STATUS_OBJECT_PATH_NOT_FOUND:
                break;
            default:
                throw se;
            }
        }

        attrExpiration = System.currentTimeMillis() + attrExpirationPeriod;

        return isExists;
    }

    /**
     * Tests to see if the file this <code>SmbFile</code> represents can be
     * read. Because any file, directory, or other resource can be read if it
     * exists, this method simply calls the <code>exists</code> method.
     *
     * @return <code>true</code> if the file is read-only
     * @throws SmbException if an error occurs while checking read permissions
     */

    public boolean canRead() throws SmbException {
        if (getType() == TYPE_NAMED_PIPE) { // try opening the pipe for reading?
            return true;
        }
        return exists(); // try opening and catch sharing violation?
    }

    /**
     * Tests to see if the file this <code>SmbFile</code> represents
     * exists and is not marked read-only. By default, resources are
     * considered to be read-only and therefore for <code>smb1://</code>,
     * <code>smb1://workgroup/</code>, and <code>smb1://server/</code> resources
     * will be read-only.
     *
     * @return  <code>true</code> if the resource exists is not marked
     *          read-only
     * @throws SmbException if an error occurs while checking write permissions
     */

    public boolean canWrite() throws SmbException {
        if (getType() == TYPE_NAMED_PIPE) { // try opening the pipe for writing?
            return true;
        }
        return exists() && (attributes & ATTR_READONLY) == 0;
    }

    /**
     * Tests to see if the file this <code>SmbFile</code> represents is a directory.
     *
     * @return <code>true</code> if this <code>SmbFile</code> is a directory
     * @throws SmbException if an error occurs while checking if this is a directory
     */

    public boolean isDirectory() throws SmbException {
        if (getUncPath0().length() == 1) {
            return true;
        }
        if (!exists()) {
            return false;
        }
        return (attributes & ATTR_DIRECTORY) == ATTR_DIRECTORY;
    }

    /**
     * Tests to see if the file this <code>SmbFile</code> represents is not a directory.
     *
     * @return <code>true</code> if this <code>SmbFile</code> is not a directory
     * @throws SmbException if an error occurs while checking if this is a file
     */

    public boolean isFile() throws SmbException {
        if (getUncPath0().length() == 1) {
            return false;
        }
        exists();
        return (attributes & ATTR_DIRECTORY) == 0;
    }

    /**
     * Tests to see if the file this SmbFile represents is marked as
     * hidden. This method will also return true for shares with names that
     * end with '$' such as <code>IPC$</code> or <code>C$</code>.
     *
     * @return <code>true</code> if the <code>SmbFile</code> is marked as being hidden
     * @throws SmbException if an error occurs while checking if this is hidden
     */

    public boolean isHidden() throws SmbException {
        if (share == null) {
            return false;
        }
        if (getUncPath0().length() == 1) {
            if (share.endsWith("$")) {
                return true;
            }
            return false;
        }
        exists();
        return (attributes & ATTR_HIDDEN) == ATTR_HIDDEN;
    }

    /**
     * If the path of this <code>SmbFile</code> falls within a DFS volume,
     * this method will return the referral path to which it maps. Otherwise
     * <code>null</code> is returned.
     */

    /**
     * Returns the DFS path for this file if it resides in a DFS share
     *
     * @return the DFS path or null if not in DFS
     * @throws SmbException if an error occurs while resolving the DFS path
     */
    public String getDfsPath() throws SmbException {
        resolveDfs(null);
        if (dfsReferral == null) {
            return null;
        }
        String path = "smb1:/" + dfsReferral.server + "/" + dfsReferral.share + unc;
        path = path.replace('\\', '/');
        if (isDirectory()) {
            path += '/';
        }
        return path;
    }

    /**
     * Retrieve the time this <code>SmbFile</code> was created. The value
     * returned is suitable for constructing a {@link java.util.Date} object
     * (i.e. seconds since Epoch 1970). Times should be the same as those
     * reported using the properties dialog of the Windows Explorer program.
     *
     * For Win95/98/Me this is actually the last write time. It is currently
     * not possible to retrieve the create time from files on these systems.
     *
     * @return The number of milliseconds since the 00:00:00 GMT, January 1,
     *         1970 as a <code>long</code> value
     * @throws SmbException if an error occurs while retrieving the creation time
     */
    public long createTime() throws SmbException {
        if (getUncPath0().length() > 1) {
            exists();
            return createTime;
        }
        return 0L;
    }

    /**
     * Retrieve the last time the file represented by this
     * <code>SmbFile</code> was modified. The value returned is suitable for
     * constructing a {@link java.util.Date} object (i.e. seconds since Epoch
     * 1970). Times should be the same as those reported using the properties
     * dialog of the Windows Explorer program.
     *
     * @return The number of milliseconds since the 00:00:00 GMT, January 1,
     *         1970 as a <code>long</code> value
     * @throws SmbException if an error occurs while retrieving the last modified time
     */
    public long lastModified() throws SmbException {
        if (getUncPath0().length() > 1) {
            exists();
            return lastModified;
        }
        return 0L;
    }

    /**
     * List the contents of this SMB resource. The list returned by this
     * method will be;
     *
     * <ul>
     * <li> files and directories contained within this resource if the
     * resource is a normal disk file directory,
     * <li> all available NetBIOS workgroups or domains if this resource is
     * the top level URL <code>smb1://</code>,
     * <li> all servers registered as members of a NetBIOS workgroup if this
     * resource refers to a workgroup in a <code>smb1://workgroup/</code> URL,
     * <li> all browseable shares of a server including printers, IPC
     * services, or disk volumes if this resource is a server URL in the form
     * <code>smb1://server/</code>,
     * <li> or <code>null</code> if the resource cannot be resolved.
     * </ul>
     *
     * @return A <code>String[]</code> array of files and directories,
     * workgroups, servers, or shares depending on the context of the
     * resource URL
     * @throws SmbException if an error occurs while listing the contents
     */
    public String[] list() throws SmbException {
        return list("*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null);
    }

    /**
     * List the contents of this SMB resource. The list returned will be
     * identical to the list returned by the parameterless <code>list()</code>
     * method minus filenames filtered by the specified filter.
     *
     * @param filter a filename filter to exclude filenames from the results
     * @return An array of filenames
     * @throws SmbException if an error occurs while listing the contents
     */
    public String[] list(final SmbFilenameFilter filter) throws SmbException {
        return list("*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, filter, null);
    }

    /**
     * List the contents of this SMB resource as an array of
     * <code>SmbFile</code> objects. This method is much more efficient than
     * the regular <code>list</code> method when querying attributes of each
     * file in the result set.
     * <p>
     * The list of <code>SmbFile</code>s returned by this method will be;
     *
     * <ul>
     * <li> files and directories contained within this resource if the
     * resource is a normal disk file directory,
     * <li> all available NetBIOS workgroups or domains if this resource is
     * the top level URL <code>smb1://</code>,
     * <li> all servers registered as members of a NetBIOS workgroup if this
     * resource refers to a workgroup in a <code>smb1://workgroup/</code> URL,
     * <li> all browseable shares of a server including printers, IPC
     * services, or disk volumes if this resource is a server URL in the form
     * <code>smb1://server/</code>,
     * <li> or <code>null</code> if the resource cannot be resolved.
     * </ul>
     *
     * @return An array of <code>SmbFile</code> objects representing file
     * and directories, workgroups, servers, or shares depending on the context
     * of the resource URL
     * @throws SmbException if an error occurs while listing the files
     */
    public SmbFile[] listFiles() throws SmbException {
        return listFiles("*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null);
    }

    /**
     * The CIFS protocol provides for DOS "wildcards" to be used as
     * a performance enhancement. The client does not have to filter
     * the names and the server does not have to return all directory
     * entries.
     * <p>
     * The wildcard expression may consist of two special meta
     * characters in addition to the normal filename characters. The '*'
     * character matches any number of characters in part of a name. If
     * the expression begins with one or more '?'s then exactly that
     * many characters will be matched whereas if it ends with '?'s
     * it will match that many characters <i>or less</i>.
     * <p>
     * Wildcard expressions will not filter workgroup names or server names.
     *
     * <blockquote><pre>
     * winnt> ls c?o*
     * clock.avi                  -rw--      82944 Mon Oct 14 1996 1:38 AM
     * Cookies                    drw--          0 Fri Nov 13 1998 9:42 PM
     * 2 items in 5ms
     * </pre></blockquote>
     *
     * @param wildcard a wildcard expression
     * @throws SmbException if an error occurs while listing the files
     * @return An array of <code>SmbFile</code> objects representing file
     * and directories, workgroups, servers, or shares depending on the context
     * of the resource URL
     */

    public SmbFile[] listFiles(final String wildcard) throws SmbException {
        return listFiles(wildcard, ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null);
    }

    /**
     * List the contents of this SMB resource. The list returned will be
     * identical to the list returned by the parameterless <code>listFiles()</code>
     * method minus files filtered by the specified filename filter.
     *
     * @param filter a filter to exclude files from the results
     * @return An array of {@code SmbFile} objects
     * @throws SmbException if an error occurs while listing the files
     */
    public SmbFile[] listFiles(final SmbFilenameFilter filter) throws SmbException {
        return listFiles("*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, filter, null);
    }

    /**
     * List the contents of this SMB resource. The list returned will be
     * identical to the list returned by the parameterless <code>listFiles()</code>
     * method minus filenames filtered by the specified filter.
     *
     * @param filter a file filter to exclude files from the results
     * @return An array of {@code SmbFile} objects
     * @throws SmbException if an error occurs while listing the files
     */
    public SmbFile[] listFiles(final SmbFileFilter filter) throws SmbException {
        return listFiles("*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, filter);
    }

    String[] list(final String wildcard, final int searchAttributes, final SmbFilenameFilter fnf, final SmbFileFilter ff)
            throws SmbException {
        final ArrayList list = new ArrayList();
        doEnum(list, false, wildcard, searchAttributes, fnf, ff);
        return (String[]) list.toArray(new String[list.size()]);
    }

    SmbFile[] listFiles(final String wildcard, final int searchAttributes, final SmbFilenameFilter fnf, final SmbFileFilter ff)
            throws SmbException {
        final ArrayList list = new ArrayList();
        doEnum(list, true, wildcard, searchAttributes, fnf, ff);
        return (SmbFile[]) list.toArray(new SmbFile[list.size()]);
    }

    void doEnum(final ArrayList list, final boolean files, String wildcard, int searchAttributes, final SmbFilenameFilter fnf,
            final SmbFileFilter ff) throws SmbException {
        if (ff instanceof final DosFileFilter dff) {
            if (dff.wildcard != null) {
                wildcard = dff.wildcard;
            }
            searchAttributes = dff.attributes;
        }

        try {
            final int hostlen = url.getHost().length();
            if (hostlen == 0 || getType() == TYPE_WORKGROUP) {
                doNetServerEnum(list, files, wildcard, searchAttributes, fnf, ff);
            } else if (share == null) {
                doShareEnum(list, files, wildcard, searchAttributes, fnf, ff);
            } else {
                doFindFirstNext(list, files, wildcard, searchAttributes, fnf, ff);
            }
        } catch (final UnknownHostException | MalformedURLException mue) {
            throw new SmbException(url.toString(), mue);
        }
    }

    void doShareEnum(final ArrayList list, final boolean files, final String wildcard, final int searchAttributes,
            final SmbFilenameFilter fnf, final SmbFileFilter ff) throws SmbException, UnknownHostException, MalformedURLException {
        final String p = url.getPath();
        IOException last = null;
        FileEntry[] entries;
        UniAddress addr;
        FileEntry e;
        HashMap map;

        if (p.lastIndexOf('/') != p.length() - 1) {
            throw new SmbException(url.toString() + " directory must end with '/'");
        }
        if (getType() != TYPE_SERVER) {
            throw new SmbException("The requested list operations is invalid: " + url.toString());
        }

        map = new HashMap();

        if (dfs.isTrustedDomain(getServer(), auth)) {
            /* The server name is actually the name of a trusted
             * domain. Add DFS roots to the list.
             */
            try {
                entries = doDfsRootEnum();
                for (final FileEntry entry : entries) {
                    e = entry;
                    if (!map.containsKey(e)) {
                        map.put(e, e);
                    }
                }
            } catch (final IOException ioe) {
                if (LogStream.level >= 4) {
                    ioe.printStackTrace(log);
                }
            }
        }

        addr = getFirstAddress();
        while (addr != null) {
            try {
                doConnect();
                try {
                    entries = doMsrpcShareEnum();
                } catch (final IOException ioe) {
                    if (LogStream.level >= 3) {
                        ioe.printStackTrace(log);
                    }
                    entries = doNetShareEnum();
                }
                for (final FileEntry entry : entries) {
                    e = entry;
                    if (!map.containsKey(e)) {
                        map.put(e, e);
                    }
                }
                break;
            } catch (final IOException ioe) {
                if (LogStream.level >= 3) {
                    ioe.printStackTrace(log);
                }
                last = ioe;
            }
            addr = getNextAddress();
        }

        if (last != null && map.isEmpty()) {
            if (!(last instanceof SmbException)) {
                throw new SmbException(url.toString(), last);
            }
            throw (SmbException) last;
        }

        final Iterator iter = map.keySet().iterator();
        while (iter.hasNext()) {
            e = (FileEntry) iter.next();
            final String name = e.getName();
            if (fnf != null && !fnf.accept(this, name)) {
                continue;
            }
            if (name.length() > 0) {
                // if !files we don't need to create SmbFiles here
                final SmbFile f = new SmbFile(this, name, e.getType(), ATTR_READONLY | ATTR_DIRECTORY, 0L, 0L, 0L);
                if (ff != null && !ff.accept(f)) {
                    continue;
                }
                if (files) {
                    list.add(f);
                } else {
                    list.add(name);
                }
            }
        }
    }

    FileEntry[] doDfsRootEnum() throws IOException {
        MsrpcDfsRootEnum rpc;
        final FileEntry[] entries;

        DcerpcHandle handle = DcerpcHandle.getHandle("ncacn_np:" + getAddress().getHostAddress() + "[\\PIPE\\netdfs]", auth);
        try {
            rpc = new MsrpcDfsRootEnum(getServer());
            handle.sendrecv(rpc);
            if (rpc.retval != 0) {
                throw new SmbException(rpc.retval, true);
            }
            return rpc.getEntries();
        } finally {
            try {
                handle.close();
            } catch (final IOException ioe) {
                if (LogStream.level >= 4) {
                    ioe.printStackTrace(log);
                }
            }
        }
    }

    FileEntry[] doMsrpcShareEnum() throws IOException {
        MsrpcShareEnum rpc;
        DcerpcHandle handle;

        rpc = new MsrpcShareEnum(url.getHost());

        /* JCIFS will build a composite list of shares if the target host has
         * multiple IP addresses such as when domain-based DFS is in play. Because
         * of this, to ensure that we query each IP individually without re-resolving
         * the hostname and getting a different IP, we must use the current addresses
         * IP rather than just url.getHost() like we were using prior to 1.2.16.
         */

        handle = DcerpcHandle.getHandle("ncacn_np:" + getAddress().getHostAddress() + "[\\PIPE\\srvsvc]", auth);

        try {
            handle.sendrecv(rpc);
            if (rpc.retval != 0) {
                throw new SmbException(rpc.retval, true);
            }
            return rpc.getEntries();
        } finally {
            try {
                handle.close();
            } catch (final IOException ioe) {
                if (LogStream.level >= 4) {
                    ioe.printStackTrace(log);
                }
            }
        }
    }

    FileEntry[] doNetShareEnum() throws SmbException {
        final SmbComTransaction req = new NetShareEnum();
        final SmbComTransactionResponse resp = new NetShareEnumResponse();

        send(req, resp);

        if (resp.status != WinError.ERROR_SUCCESS) {
            throw new SmbException(resp.status, true);
        }

        return resp.results;
    }

    void doNetServerEnum(final ArrayList list, final boolean files, final String wildcard, final int searchAttributes,
            final SmbFilenameFilter fnf, final SmbFileFilter ff) throws SmbException, UnknownHostException, MalformedURLException {
        final int listType = url.getHost().length() == 0 ? 0 : getType();
        SmbComTransaction req;
        SmbComTransactionResponse resp;

        if (listType == 0) {
            connect0();
            req = new NetServerEnum2(tree.session.transport.server.oemDomainName, NetServerEnum2.SV_TYPE_DOMAIN_ENUM);
        } else if (listType == TYPE_WORKGROUP) {
            req = new NetServerEnum2(url.getHost(), NetServerEnum2.SV_TYPE_ALL);
        } else {
            throw new SmbException("The requested list operations is invalid: " + url.toString());
        }
        resp = new NetServerEnum2Response();

        boolean more;
        do {
            int n;

            send(req, resp);

            if (resp.status != WinError.ERROR_SUCCESS && resp.status != WinError.ERROR_MORE_DATA) {
                throw new SmbException(resp.status, true);
            }
            more = resp.status == WinError.ERROR_MORE_DATA;

            n = more ? resp.numEntries - 1 : resp.numEntries;
            for (int i = 0; i < n; i++) {
                final FileEntry e = resp.results[i];
                final String name = e.getName();
                if (fnf != null && !fnf.accept(this, name)) {
                    continue;
                }
                if (name.length() > 0) {
                    // if !files we don't need to create SmbFiles here
                    final SmbFile f = new SmbFile(this, name, e.getType(), ATTR_READONLY | ATTR_DIRECTORY, 0L, 0L, 0L);
                    if (ff != null && !ff.accept(f)) {
                        continue;
                    }
                    if (files) {
                        list.add(f);
                    } else {
                        list.add(name);
                    }
                }
            }
            if (getType() != TYPE_WORKGROUP) {
                break;
            }
            req.subCommand = (byte) SmbComTransaction.NET_SERVER_ENUM3;
            req.reset(0, ((NetServerEnum2Response) resp).lastName);
            resp.reset();
        } while (more);
    }

    void doFindFirstNext(final ArrayList list, final boolean files, final String wildcard, final int searchAttributes,
            final SmbFilenameFilter fnf, final SmbFileFilter ff) throws SmbException, UnknownHostException, MalformedURLException {
        SmbComTransaction req;
        Trans2FindFirst2Response resp;
        int sid;
        final String path = getUncPath0();
        final String p = url.getPath();

        if (p.lastIndexOf('/') != p.length() - 1) {
            throw new SmbException(url.toString() + " directory must end with '/'");
        }

        req = new Trans2FindFirst2(path, wildcard, searchAttributes);
        resp = new Trans2FindFirst2Response();

        if (LogStream.level >= 3) {
            log.println("doFindFirstNext: " + req.path);
        }

        send(req, resp);

        sid = resp.sid;
        req = new Trans2FindNext2(sid, resp.resumeKey, resp.lastName);

        /* The only difference between first2 and next2 responses is subCommand
         * so let's recycle the response object.
         */
        resp.subCommand = SmbComTransaction.TRANS2_FIND_NEXT2;

        for (;;) {
            for (int i = 0; i < resp.numEntries; i++) {
                final FileEntry e = resp.results[i];
                final String name = e.getName();
                if (name.length() < 3) {
                    final int h = name.hashCode();
                    if ((h == HASH_DOT || h == HASH_DOT_DOT) && (name.equals(".") || name.equals(".."))) {
                        continue;
                    }
                }
                if (fnf != null && !fnf.accept(this, name)) {
                    continue;
                }
                if (name.length() > 0) {
                    final SmbFile f =
                            new SmbFile(this, name, TYPE_FILESYSTEM, e.getAttributes(), e.createTime(), e.lastModified(), e.length());
                    if (ff != null && !ff.accept(f)) {
                        continue;
                    }
                    if (files) {
                        list.add(f);
                    } else {
                        list.add(name);
                    }
                }
            }

            if (resp.isEndOfSearch || resp.numEntries == 0) {
                break;
            }

            req.reset(resp.resumeKey, resp.lastName);
            resp.reset();
            send(req, resp);
        }

        try {
            send(new SmbComFindClose2(sid), blank_resp());
        } catch (final SmbException se) {
            if (LogStream.level >= 4) {
                se.printStackTrace(log);
            }
        }
    }

    /**
     * Changes the name of the file this <code>SmbFile</code> represents to the name
     * designated by the <code>SmbFile</code> argument.
     *
     * <i>Remember: <code>SmbFile</code>s are immutible and therefore
     * the path associated with this <code>SmbFile</code> object will not
     * change). To access the renamed file it is necessary to construct a
     * new {@code SmbFile}</i>.
     *
     * @param  dest  An <code>SmbFile</code> that represents the new pathname
     * @throws NullPointerException
     *         If the <code>dest</code> argument is <code>null</code>
     * @throws SmbException if an error occurs during the rename operation
     */
    public void renameTo(final SmbFile dest) throws SmbException {
        if (getUncPath0().length() == 1 || dest.getUncPath0().length() == 1) {
            throw new SmbException("Invalid operation for workgroups, servers, or shares");
        }

        resolveDfs(null);
        dest.resolveDfs(null);

        if (!tree.equals(dest.tree)) {
            throw new SmbException("Invalid operation for workgroups, servers, or shares");
        }

        if (LogStream.level >= 3) {
            log.println("renameTo: " + unc + " -> " + dest.unc);
        }

        attrExpiration = sizeExpiration = 0;
        dest.attrExpiration = 0;

        /*
         * Rename Request / Response
         */

        send(new SmbComRename(unc, dest.unc), blank_resp());
    }

    class WriterThread extends Thread {
        byte[] b;
        int n;
        long off;
        boolean ready;
        SmbFile dest;
        SmbException e = null;
        boolean useNTSmbs;
        SmbComWriteAndX reqx;
        SmbComWrite req;
        ServerMessageBlock resp;

        WriterThread() throws SmbException {
            super("JCIFS-WriterThread");
            useNTSmbs = tree.session.transport.hasCapability(SmbConstants.CAP_NT_SMBS);
            if (useNTSmbs) {
                reqx = new SmbComWriteAndX();
                resp = new SmbComWriteAndXResponse();
            } else {
                req = new SmbComWrite();
                resp = new SmbComWriteResponse();
            }
            ready = false;
        }

        synchronized void write(final byte[] b, final int n, final SmbFile dest, final long off) {
            this.b = b;
            this.n = n;
            this.dest = dest;
            this.off = off;
            ready = false;
            notify();
        }

        @Override
        public void run() {
            synchronized (this) {
                try {
                    for (;;) {
                        notify();
                        ready = true;
                        while (ready) {
                            wait();
                        }
                        if (n == -1) {
                            return;
                        }
                        if (useNTSmbs) {
                            reqx.setParam(dest.fid, off, n, b, 0, n);
                            dest.send(reqx, resp);
                        } else {
                            req.setParam(dest.fid, off, n, b, 0, n);
                            dest.send(req, resp);
                        }
                    }
                } catch (final SmbException e) {
                    this.e = e;
                } catch (final Exception x) {
                    this.e = new SmbException("WriterThread", x);
                }
                notify();
            }
        }
    }

    void copyTo0(final SmbFile dest, final byte[][] b, final int bsize, final WriterThread w, final SmbComReadAndX req,
            final SmbComReadAndXResponse resp) throws SmbException {
        int i;

        if (attrExpiration < System.currentTimeMillis()) {
            attributes = ATTR_READONLY | ATTR_DIRECTORY;
            createTime = 0L;
            lastModified = 0L;
            isExists = false;

            final Info info = queryPath(getUncPath0(), Trans2QueryPathInformationResponse.SMB_QUERY_FILE_BASIC_INFO);
            attributes = info.getAttributes();
            createTime = info.getCreateTime();
            lastModified = info.getLastWriteTime();

            /* If any of the above fails, isExists will not be set true
             */

            isExists = true;
            attrExpiration = System.currentTimeMillis() + attrExpirationPeriod;
        }

        if (isDirectory()) {
            SmbFile[] files;
            SmbFile ndest;

            final String path = dest.getUncPath0();
            if (path.length() > 1) {
                try {
                    dest.mkdir();
                    dest.setPathInformation(attributes, createTime, lastModified);
                } catch (final SmbException se) {
                    if (se.getNtStatus() != NtStatus.NT_STATUS_ACCESS_DENIED
                            && se.getNtStatus() != NtStatus.NT_STATUS_OBJECT_NAME_COLLISION) {
                        throw se;
                    }
                }
            }

            files = listFiles("*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null);
            try {
                for (i = 0; i < files.length; i++) {
                    ndest = new SmbFile(dest, files[i].getName(), files[i].type, files[i].attributes, files[i].createTime,
                            files[i].lastModified, files[i].size);
                    files[i].copyTo0(ndest, b, bsize, w, req, resp);
                }
            } catch (final UnknownHostException | MalformedURLException mue) {
                throw new SmbException(url.toString(), mue);
            }
        } else {
            long off;

            try {
                open(SmbFile.O_RDONLY, 0, ATTR_NORMAL, 0);
                try {
                    dest.open(SmbFile.O_CREAT | SmbFile.O_WRONLY | SmbFile.O_TRUNC, FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES, attributes, 0);
                } catch (final SmbAuthException sae) {
                    if ((dest.attributes & ATTR_READONLY) == 0) {
                        throw sae;
                    }
                    /* Remove READONLY and try again
                     */
                    dest.setPathInformation(dest.attributes & ~ATTR_READONLY, 0L, 0L);
                    dest.open(SmbFile.O_CREAT | SmbFile.O_WRONLY | SmbFile.O_TRUNC, FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES, attributes, 0);
                }

                i = 0;
                off = 0L;
                for (;;) {
                    req.setParam(fid, off, bsize);
                    resp.setParam(b[i], 0);
                    send(req, resp);

                    synchronized (w) {
                        if (w.e != null) {
                            throw w.e;
                        }
                        while (!w.ready) {
                            try {
                                w.wait();
                            } catch (final InterruptedException ie) {
                                throw new SmbException(dest.url.toString(), ie);
                            }
                        }
                        if (w.e != null) {
                            throw w.e;
                        }
                        if (resp.dataLength <= 0) {
                            break;
                        }
                        w.write(b[i], resp.dataLength, dest, off);
                    }

                    i = i == 1 ? 0 : 1;
                    off += resp.dataLength;
                }

                dest.send(new Trans2SetFileInformation(dest.fid, attributes, createTime, lastModified),
                        new Trans2SetFileInformationResponse());
                dest.close(0L);
            } catch (final SmbException se) {

                if (!ignoreCopyToException) {
                    throw new SmbException("Failed to copy file from [" + this.toString() + "] to [" + dest.toString() + "]", se);
                }

                if (LogStream.level > 1) {
                    se.printStackTrace(log);
                }
            } finally {
                close();
            }
        }
    }

    /**
     * This method will copy the file or directory represented by this
     * {@code SmbFile} and it's sub-contents to the location specified by the
     * {@code dest} parameter. This file and the destination file do not
     * need to be on the same host. This operation does not copy extended
     * file attibutes such as ACLs but it does copy regular attributes as
     * well as create and last write times. This method is almost twice as
     * efficient as manually copying as it employs an additional write
     * thread to read and write data concurrently.
     *
     * It is not possible (nor meaningful) to copy entire workgroups or
     * servers.
     *
     * @param dest the destination file or directory
     * @throws SmbException if an error occurs during the copy operation
     */
    public void copyTo(final SmbFile dest) throws SmbException {
        SmbComReadAndX req;
        SmbComReadAndXResponse resp;
        WriterThread w;
        int bsize;
        byte[][] b;

        /* Should be able to copy an entire share actually
         */
        if (share == null || dest.share == null) {
            throw new SmbException("Invalid operation for workgroups or servers");
        }

        req = new SmbComReadAndX();
        resp = new SmbComReadAndXResponse();

        connect0();
        dest.connect0();

        /* At this point the maxBufferSize values are from the server
         * exporting the volumes, not the one that we will actually
         * end up performing IO with. If the server hosting the
         * actual files has a smaller maxBufSize this could be
         * incorrect. To handle this properly it is necessary
         * to redirect the tree to the target server first before
         * establishing buffer size. These exists() calls facilitate
         * that.
         */
        resolveDfs(null);

        /* It is invalid for the source path to be a child of the destination
         * path or visa versa.
         */
        try {
            if (getAddress().equals(dest.getAddress())
                    && canon.regionMatches(true, 0, dest.canon, 0, Math.min(canon.length(), dest.canon.length()))) {
                throw new SmbException("Source and destination paths overlap.");
            }
        } catch (final UnknownHostException uhe) {}

        w = new WriterThread();
        w.setDaemon(true);
        w.start();

        /* Downgrade one transport to the lower of the negotiated buffer sizes
         * so we can just send whatever is received.
         */

        final SmbTransport t1 = tree.session.transport;
        final SmbTransport t2 = dest.tree.session.transport;

        if (t1.snd_buf_size < t2.snd_buf_size) {
            t2.snd_buf_size = t1.snd_buf_size;
        } else {
            t1.snd_buf_size = t2.snd_buf_size;
        }

        bsize = Math.min(t1.rcv_buf_size - 70, t1.snd_buf_size - 70);
        b = new byte[2][bsize];

        try {
            copyTo0(dest, b, bsize, w, req, resp);
        } finally {
            w.write(null, -1, null, 0);
        }
    }

    /**
     * This method will delete the file or directory specified by this
     * <code>SmbFile</code>. If the target is a directory, the contents of
     * the directory will be deleted as well. If a file within the directory or
     * it's sub-directories is marked read-only, the read-only status will
     * be removed and the file will be deleted.
     *
     * @throws SmbException if an error occurs while deleting the file or directory
     */
    public void delete() throws SmbException {
        exists();
        getUncPath0();
        delete(unc);
    }

    void delete(final String fileName) throws SmbException {
        if (getUncPath0().length() == 1) {
            throw new SmbException("Invalid operation for workgroups, servers, or shares");
        }

        if (System.currentTimeMillis() > attrExpiration) {
            attributes = ATTR_READONLY | ATTR_DIRECTORY;
            createTime = 0L;
            lastModified = 0L;
            isExists = false;

            final Info info = queryPath(getUncPath0(), Trans2QueryPathInformationResponse.SMB_QUERY_FILE_BASIC_INFO);
            attributes = info.getAttributes();
            createTime = info.getCreateTime();
            lastModified = info.getLastWriteTime();

            attrExpiration = System.currentTimeMillis() + attrExpirationPeriod;
            isExists = true;
        }

        if ((attributes & ATTR_READONLY) != 0) {
            setReadWrite();
        }

        /*
         * Delete or Delete Directory Request / Response
         */

        if (LogStream.level >= 3) {
            log.println("delete: " + fileName);
        }

        if ((attributes & ATTR_DIRECTORY) != 0) {

            /* Recursively delete directory contents
             */

            try {
                final SmbFile[] l = listFiles("*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null);
                for (final SmbFile element : l) {
                    element.delete();
                }
            } catch (final SmbException se) {
                /* Oracle FilesOnline version 9.0.4 doesn't send '.' and '..' so
                 * listFiles may generate undesireable "cannot find
                 * the file specified".
                 */
                if (se.getNtStatus() != NtStatus.NT_STATUS_NO_SUCH_FILE) {
                    throw se;
                }
            }

            send(new SmbComDeleteDirectory(fileName), blank_resp());
        } else {
            send(new SmbComDelete(fileName), blank_resp());
        }

        attrExpiration = sizeExpiration = 0;
    }

    /**
     * Returns the length of this {@code SmbFile} in bytes. If this object
     * is a {@code TYPE_SHARE} the total capacity of the disk shared in
     * bytes is returned. If this object is a directory or a type other than
     * {@code TYPE_SHARE}, 0L is returned.
     *
     * @return The length of the file in bytes or 0 if this
     * <code>SmbFile</code> is not a file.
     * @throws SmbException if an error occurs while retrieving the file length
     */

    public long length() throws SmbException {
        if (sizeExpiration > System.currentTimeMillis()) {
            return size;
        }

        if (getType() == TYPE_SHARE) {
            Trans2QueryFSInformationResponse response;
            final int level = Trans2QueryFSInformationResponse.SMB_INFO_ALLOCATION;

            response = new Trans2QueryFSInformationResponse(level);
            send(new Trans2QueryFSInformation(level), response);

            size = response.info.getCapacity();
        } else if (getUncPath0().length() > 1 && type != TYPE_NAMED_PIPE) {
            final Info info = queryPath(getUncPath0(), Trans2QueryPathInformationResponse.SMB_QUERY_FILE_STANDARD_INFO);
            size = info.getSize();
        } else {
            size = 0L;
        }
        sizeExpiration = System.currentTimeMillis() + attrExpirationPeriod;
        return size;
    }

    /**
     * This method returns the free disk space in bytes of the drive this share
     * represents or the drive on which the directory or file resides. Objects
     * other than {@code TYPE_SHARE} or {@code TYPE_FILESYSTEM} will result
     * in 0L being returned.
     *
     * @return the free disk space in bytes of the drive on which this file or
     * directory resides
     * @throws SmbException if an error occurs while retrieving disk space information
     */
    public long getDiskFreeSpace() throws SmbException {
        if (getType() == TYPE_SHARE || type == TYPE_FILESYSTEM) {
            int level = Trans2QueryFSInformationResponse.SMB_FS_FULL_SIZE_INFORMATION;
            try {
                return queryFSInformation(level);
            } catch (final SmbException ex) {
                switch (ex.getNtStatus()) {
                case NtStatus.NT_STATUS_INVALID_INFO_CLASS:
                case NtStatus.NT_STATUS_UNSUCCESSFUL: // NetApp Filer
                    // SMB_FS_FULL_SIZE_INFORMATION not supported by the server.
                    level = Trans2QueryFSInformationResponse.SMB_INFO_ALLOCATION;
                    return queryFSInformation(level);
                }
                throw ex;
            }
        }
        return 0L;
    }

    private long queryFSInformation(final int level) throws SmbException {
        Trans2QueryFSInformationResponse response = new Trans2QueryFSInformationResponse(level);
        send(new Trans2QueryFSInformation(level), response);

        if (type == TYPE_SHARE) {
            size = response.info.getCapacity();
            sizeExpiration = System.currentTimeMillis() + attrExpirationPeriod;
        }

        return response.info.getFree();
    }

    /**
     * Creates a directory with the path specified by this
     * <code>SmbFile</code>. For this method to be successful, the target
     * must not already exist. This method will fail when
     * used with <code>smb1://</code>, <code>smb1://workgroup/</code>,
     * <code>smb1://server/</code>, or <code>smb1://server/share/</code> URLs
     * because workgroups, servers, and shares cannot be dynamically created
     * (although in the future it may be possible to create shares).
     *
     * @throws SmbException if an error occurs while creating the directory
     */
    public void mkdir() throws SmbException {
        final String path = getUncPath0();

        if (path.length() == 1) {
            throw new SmbException("Invalid operation for workgroups, servers, or shares");
        }

        /*
         * Create Directory Request / Response
         */

        if (LogStream.level >= 3) {
            log.println("mkdir: " + path);
        }

        send(new SmbComCreateDirectory(path), blank_resp());

        attrExpiration = sizeExpiration = 0;
    }

    /**
     * Creates a directory with the path specified by this {@code SmbFile}
     * and any parent directories that do not exist. This method will fail
     * when used with <code>smb1://</code>, <code>smb1://workgroup/</code>,
     * <code>smb1://server/</code>, or <code>smb1://server/share/</code> URLs
     * because workgroups, servers, and shares cannot be dynamically created
     * (although in the future it may be possible to create shares).
     *
     * @throws SmbException if an error occurs while creating the directories
     */
    public void mkdirs() throws SmbException {
        SmbFile parent;

        try {
            parent = new SmbFile(getParent(), auth);
        } catch (final IOException ioe) {
            return;
        }
        if (!parent.exists()) {
            parent.mkdirs();
        }
        mkdir();
    }

    /**
     * Create a new file but fail if it already exists. The check for
     * existance of the file and it's creation are an atomic operation with
     * respect to other filesystem activities.
     * @throws SmbException if an error occurs while creating the file
     */
    public void createNewFile() throws SmbException {
        if (getUncPath0().length() == 1) {
            throw new SmbException("Invalid operation for workgroups, servers, or shares");
        }
        close(open0(O_RDWR | O_CREAT | O_EXCL, 0, ATTR_NORMAL, 0), 0L);
    }

    void setPathInformation(final int attrs, final long ctime, final long mtime) throws SmbException {
        int f, dir;

        exists();
        dir = attributes & ATTR_DIRECTORY;

        f = open0(O_RDONLY, FILE_WRITE_ATTRIBUTES, dir, dir != 0 ? 0x0001 : 0x0040);
        send(new Trans2SetFileInformation(f, attrs | dir, ctime, mtime), new Trans2SetFileInformationResponse());
        close(f, 0L);

        attrExpiration = 0;
    }

    /**
     * Set the create time of the file. The time is specified as milliseconds
     * from Jan 1, 1970 which is the same as that which is returned by the
     * {@code createTime()} method.
     *
     * This method does not apply to workgroups, servers, or shares.
     *
     * @param time the create time as milliseconds since Jan 1, 1970
     * @throws SmbException if an error occurs while setting the create time
     */
    public void setCreateTime(final long time) throws SmbException {
        if (getUncPath0().length() == 1) {
            throw new SmbException("Invalid operation for workgroups, servers, or shares");
        }

        setPathInformation(0, time, 0L);
    }

    /**
     * Set the last modified time of the file. The time is specified as milliseconds
     * from Jan 1, 1970 which is the same as that which is returned by the
     * {@code lastModified()}, {@code getLastModified()}, and {@code getDate()} methods.
     *
     * This method does not apply to workgroups, servers, or shares.
     *
     * @param time the last modified time as milliseconds since Jan 1, 1970
     * @throws SmbException if an error occurs while setting the last modified time
     */
    public void setLastModified(final long time) throws SmbException {
        if (getUncPath0().length() == 1) {
            throw new SmbException("Invalid operation for workgroups, servers, or shares");
        }

        setPathInformation(0, 0L, time);
    }

    /**
     * Return the attributes of this file. Attributes are represented as a
     * bitset that must be masked with {@code ATTR_*} constants to determine
     * if they are set or unset. The value returned is suitable for use with
     * the {@code setAttributes()} method.
     *
     * @return the {@code ATTR_*} attributes associated with this file
     * @throws SmbException if an error occurs while retrieving attributes
     */
    public int getAttributes() throws SmbException {
        if (getUncPath0().length() == 1) {
            return 0;
        }
        exists();
        return attributes & ATTR_GET_MASK;
    }

    /**
     * Set the attributes of this file. Attributes are composed into a
     * bitset by bitwise ORing the {@code ATTR_*} constants. Setting the
     * value returned by {@code getAttributes} will result in both files
     * having the same attributes.
     * @param attrs the attributes to set
     * @throws SmbException if an error occurs while setting attributes
     */
    public void setAttributes(final int attrs) throws SmbException {
        if (getUncPath0().length() == 1) {
            throw new SmbException("Invalid operation for workgroups, servers, or shares");
        }
        setPathInformation(attrs & ATTR_SET_MASK, 0L, 0L);
    }

    /**
     * Make this file read-only. This is shorthand for {@code setAttributes(
     * getAttributes() | ATTR_READ_ONLY )}.
     *
     * @throws SmbException if an error occurs while setting the read-only flag
     */
    public void setReadOnly() throws SmbException {
        setAttributes(getAttributes() | ATTR_READONLY);
    }

    /**
     * Turn off the read-only attribute of this file. This is shorthand for
     * {@code setAttributes( getAttributes() &amp; ~ATTR_READONLY )}.
     *
     * @throws SmbException if an error occurs while clearing the read-only flag
     */
    public void setReadWrite() throws SmbException {
        setAttributes(getAttributes() & ~ATTR_READONLY);
    }

    /**
     * Returns a {@link java.net.URL} for this <code>SmbFile</code>. The
     * <code>URL</code> may be used as any other <code>URL</code> might to
     * access an SMB resource. Currently only retrieving data and information
     * is supported (i.e. no {@code doOutput}).
     *
     * @deprecated Use getURL() instead
     * @return A new <code>{@link java.net.URL}</code> for this <code>SmbFile</code>
     * @throws MalformedURLException if the URL is malformed
     */
    @Deprecated
    public URL toURL() throws MalformedURLException {
        return url;
    }

    /**
     * Computes a hashCode for this file based on the URL string and IP
     * address if the server. The hashing function uses the hashcode of the
     * server address, the canonical representation of the URL, and does not
     * compare authentication information. In essance, two
     * <code>SmbFile</code> objects that refer to
     * the same file should generate the same hashcode provided it is possible
     * to make such a determination.
     *
     * @return  A hashcode for this abstract file
     */

    @Override
    public int hashCode() {
        int hash;
        try {
            hash = getAddress().hashCode();
        } catch (final UnknownHostException uhe) {
            hash = getServer().toUpperCase().hashCode();
        }
        getUncPath0();
        return hash + canon.toUpperCase().hashCode();
    }

    /**
     * Checks if two path names could possibly be equal
     *
     * @param path1 the first path
     * @param path2 the second path
     * @return true if the paths could be equal
     */
    protected boolean pathNamesPossiblyEqual(final String path1, final String path2) {
        int p1, p2, l1, l2;

        // if unsure return this method returns true

        p1 = path1.lastIndexOf('/');
        p2 = path2.lastIndexOf('/');
        l1 = path1.length() - p1;
        l2 = path2.length() - p2;

        // anything with dots voids comparison
        if ((l1 > 1 && path1.charAt(p1 + 1) == '.') || (l2 > 1 && path2.charAt(p2 + 1) == '.')) {
            return true;
        }

        return l1 == l2 && path1.regionMatches(true, p1, path2, p2, l1);
    }

    /**
     * Tests to see if two <code>SmbFile</code> objects are equal. Two
     * SmbFile objects are equal when they reference the same SMB
     * resource. More specifically, two <code>SmbFile</code> objects are
     * equals if their server IP addresses are equal and the canonicalized
     * representation of their URLs, minus authentication parameters, are
     * case insensitivly and lexographically equal.
     *
     * For example, assuming the server <code>angus</code> resolves to the
     * <code>192.168.1.15</code> IP address, the below URLs would result in
     * <code>SmbFile</code>s that are equal.
     *
     * <blockquote><pre>
     * smb1://192.168.1.15/share/DIR/foo.txt
     * smb1://angus/share/data/../dir/foo.txt
     * </pre></blockquote>
     *
     * @param   obj Another <code>SmbFile</code> object to compare for equality
     * @return  <code>true</code> if the two objects refer to the same SMB resource
     *          and <code>false</code> otherwise
     */

    @Override
    public boolean equals(final Object obj) {
        if (obj instanceof final SmbFile f) {
            boolean ret;

            if (this == f) {
                return true;
            }

            /* If uncertain, pathNamesPossiblyEqual returns true.
             * Comparing canonical paths is definitive.
             */
            if (pathNamesPossiblyEqual(url.getPath(), f.url.getPath())) {

                getUncPath0();
                f.getUncPath0();

                if (canon.equalsIgnoreCase(f.canon)) {
                    try {
                        ret = getAddress().equals(f.getAddress());
                    } catch (final UnknownHostException uhe) {
                        ret = getServer().equalsIgnoreCase(f.getServer());
                    }
                    return ret;
                }
            }
        }

        return false;
    }
    /*
    public boolean equals( Object obj ) {
        return obj instanceof SmbFile && obj.hashCode() == hashCode();
    }
    */

    /**
     * Returns the string representation of this SmbFile object. This will
     * be the same as the URL used to construct this <code>SmbFile</code>.
     * This method will return the same value
     * as <code>getPath</code>.
     *
     * @return  The original URL representation of this SMB resource
     */

    @Override
    public String toString() {
        return Strings.maskSecretValue(url.toString());
    }

    /* URLConnection implementation */
    /**
     * This URLConnection method just returns the result of {@code length()}.
     *
     * @return the length of this file or 0 if it refers to a directory
     */

    @Override
    public int getContentLength() {
        try {
            return (int) (length() & 0xFFFFFFFFL);
        } catch (final SmbException se) {}
        return 0;
    }

    /**
     * This URLConnection method just returns the result of {@code lastModified}.
     *
     * @return the last modified data as milliseconds since Jan 1, 1970
     */
    @Override
    public long getDate() {
        try {
            return lastModified();
        } catch (final SmbException se) {}
        return 0L;
    }

    /**
     * This URLConnection method just returns the result of {@code lastModified}.
     *
     * @return the last modified data as milliseconds since Jan 1, 1970
     */
    @Override
    public long getLastModified() {
        try {
            return lastModified();
        } catch (final SmbException se) {}
        return 0L;
    }

    /**
     * This URLConnection method just returns a new {@code SmbFileInputStream} created with this file.
     *
     * @throws IOException thrown by {@code SmbFileInputStream} constructor
     */
    @Override
    public InputStream getInputStream() throws IOException {
        return new SmbFileInputStream(this);
    }

    /**
     * This URLConnection method just returns a new {@code SmbFileOutputStream} created with this file.
     *
     * @throws IOException thrown by {@code SmbFileOutputStream} constructor
     */
    @Override
    public OutputStream getOutputStream() throws IOException {
        return new SmbFileOutputStream(this);
    }

    private void processAces(final ACE[] aces, final boolean resolveSids) throws IOException {
        final String server = getServerWithDfs();
        int ai;

        if (resolveSids) {
            final SID[] sids = new SID[aces.length];
            final String[] names = null;

            for (ai = 0; ai < aces.length; ai++) {
                sids[ai] = aces[ai].sid;
            }

            for (int off = 0; off < sids.length; off += 64) {
                int len = sids.length - off;
                if (len > 64) {
                    len = 64;
                }
                SID.resolveSids(server, auth, sids, off, len);
            }
        } else {
            for (ai = 0; ai < aces.length; ai++) {
                aces[ai].sid.origin_server = server;
                aces[ai].sid.origin_auth = auth;
            }
        }
    }

    /**
     * Return an array of Access Control Entry (ACE) objects representing
     * the security descriptor associated with this file or directory.
     * If no DACL is present, null is returned. If the DACL is empty, an array with 0 elements is returned.
     * @param resolveSids Attempt to resolve the SIDs within each ACE from
     * their numeric representation to their corresponding account names.
     * @return an array of ACE entries for this file
     * @throws IOException if an I/O error occurs
     */
    public ACE[] getSecurity(final boolean resolveSids) throws IOException {
        int f;
        ACE[] aces;

        f = open0(O_RDONLY, READ_CONTROL, 0, isDirectory() ? 1 : 0);

        /*
         * NtTrans Query Security Desc Request / Response
         */

        final NtTransQuerySecurityDesc request = new NtTransQuerySecurityDesc(f, 0x04);
        final NtTransQuerySecurityDescResponse response = new NtTransQuerySecurityDescResponse();

        try {
            send(request, response);
            aces = response.securityDescriptor.aces;
            if (aces != null) {
                processAces(aces, resolveSids);
            }
        } finally {
            try {
                close(f, 0L);
            } catch (final SmbException se) {
                if (LogStream.level >= 1) {
                    se.printStackTrace(log);
                }
            }
        }

        return aces;
    }

    /**
     * Return an array of Access Control Entry (ACE) objects representing
     * the share permissions on the share exporting this file or directory.
     * If no DACL is present, null is returned. If the DACL is empty, an array with 0 elements is returned.
     * <p>
     * Note that this is different from calling {@code getSecurity} on a
     * share. There are actually two different ACLs for shares - the ACL on
     * the share and the ACL on the folder being shared.
     * Go to <i>Computer Management</i>
     * &gt; <i>System Tools</i> &gt; <i>Shared Folders</i> &gt; <i>Shares</i> and
     * look at the <i>Properties</i> for a share. You will see two tabs - one
     * for "Share Permissions" and another for "Security". These correspond to
     * the ACLs returned by {@code getShareSecurity} and {@code getSecurity}
     * respectively.
     * @param resolveSids Attempt to resolve the SIDs within each ACE form
     * their numeric representation to their corresponding account names.
     */
    /**
     * Returns the share security information for this share
     *
     * @param resolveSids whether to resolve SIDs to names
     * @return an array of ACE entries for this share
     * @throws IOException if an I/O error occurs
     */
    public ACE[] getShareSecurity(final boolean resolveSids) throws IOException {
        final String p = url.getPath();
        MsrpcShareGetInfo rpc;
        DcerpcHandle handle;
        ACE[] aces;

        resolveDfs(null);
        final String server = getServerWithDfs();

        rpc = new MsrpcShareGetInfo(server, tree.share);
        handle = DcerpcHandle.getHandle("ncacn_np:" + server + "[\\PIPE\\srvsvc]", auth);

        try {
            handle.sendrecv(rpc);
            if (rpc.retval != 0) {
                throw new SmbException(rpc.retval, true);
            }
            aces = rpc.getSecurity();
            if (aces != null) {
                processAces(aces, resolveSids);
            }
        } finally {
            try {
                handle.close();
            } catch (final IOException ioe) {
                if (LogStream.level >= 1) {
                    ioe.printStackTrace(log);
                }
            }
        }

        return aces;
    }

    /**
     * Return an array of Access Control Entry (ACE) objects representing
     * the security descriptor associated with this file or directory.
     * <p>
     * Initially, the SIDs within each ACE will not be resolved however when
     * {@code getType()}, {@code getDomainName()}, {@code getAccountName()},
     * or {@code toString()} is called, the names will attempt to be
     * resolved. If the names cannot be resolved (e.g. due to temporary
     * network failure), the said methods will return default values (usually
     * {@code S-X-Y-Z} strings of fragments of).
     * <p>
     * Alternatively {@code getSecurity(true)} may be used to resolve all
     * SIDs together and detect network failures.
     */
    /**
     * Returns the security information for this file
     *
     * @return an array of ACE entries for this file
     * @throws IOException if an I/O error occurs
     */
    public ACE[] getSecurity() throws IOException {
        return getSecurity(false);
    }

    /**
     * Returns the owner user SID for this file
     *
     * @return the owner user SID
     * @throws IOException if an I/O error occurs
     */
    public SID getOwnerUser() throws IOException {

        final int f = open0(O_RDONLY, READ_CONTROL, 0, isDirectory() ? 1 : 0);

        /*
         * NtTrans Query Security Desc Request / Response
         */
        final NtTransQuerySecurityDesc request = new NtTransQuerySecurityDesc(f, 0x01);
        final NtTransQuerySecurityDescResponse response = new NtTransQuerySecurityDescResponse();
        send(request, response);

        close(f, 0L);

        response.securityDescriptor.owner_user.resolve(getServerWithDfs(), auth);
        return response.securityDescriptor.owner_user;
    }

    /**
     * Returns the owner group SID for this file
     *
     * @return the owner group SID
     * @throws IOException if an I/O error occurs
     */
    public SID getOwnerGroup() throws IOException {

        final int f = open0(O_RDONLY, READ_CONTROL, 0, isDirectory() ? 1 : 0);

        /*
         * NtTrans Query Security Desc Request / Response
         */
        final NtTransQuerySecurityDesc request = new NtTransQuerySecurityDesc(f, 0x02);
        final NtTransQuerySecurityDescResponse response = new NtTransQuerySecurityDescResponse();
        send(request, response);

        close(f, 0L);

        response.securityDescriptor.owner_group.resolve(getServerWithDfs(), auth);
        return response.securityDescriptor.owner_group;
    }

}
